package manager;

import recipe.RecipeData;

import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.imageio.ImageIO;

/**
 * Class - FileManager
 * 
 * Description - Class to perform file management functions
 * @author Ryne Okimoto
 *
 */
public class FileManager {
	// Class data
	private static final String BEGIN        = "#BEG#";
	private static final String END          = "#END#";
	private static final String NAME         = "#NAM#";
	private static final String TAG          = "#TAG#";
	private static final String CATEGORY     = "#CAT#";
	private static final String IMAGE        = "#IMG#";
	
	private static final String INGREDIENTS   = "ING#";
	private static final String DIRECTIONS    = "DIR#";
	private static final String RECIPE        = "REC#";
	
	private static final String RECIPE_PATH  = "./rcp_db/Rcp_Files/";
	private static final String IMAGE_PATH   = "./rcp_db/Img_Files/";
	
	private RecipeData recipeObject;
	private File recipeFile;
	private String fileName;
	private String imageName;
	private BufferedReader rcpReader;
	private BufferedWriter rcpWriter;
	private BufferedImage rcpImage;
	
	/**
	 * Default constructor
	 */
	public FileManager(){
		recipeFile = null;
		recipeFile = null;
		fileName = null;
		imageName = null;
		rcpReader = null;
		rcpWriter = null;
		rcpImage = null;
	} // constructor
	
	/**
	 * Overloaded constructor
	 * 
	 * Description: Constructor that accepts a filename to read/write data
	 * @param fileName
	 */
	public FileManager(String fileName){
		this.fileName = RECIPE_PATH + fileName;
		recipeObject = new RecipeData();
		rcpReader = null;
		rcpWriter = null;
		rcpImage = null;
		openFile();
		readFile();
		
		imageName = getImageFromRecipe(recipeObject);
	} // overloaded constructor
	
	/**
	 * Overloaded constructor
	 * @param fileName
	 * @param recipeFile
	 */
	public FileManager(String fileName, File recipeFile){
		this.fileName = RECIPE_PATH + fileName;
		this.recipeFile = recipeFile;
		recipeObject = new RecipeData();
		rcpWriter = null;
		rcpReader = null;
		rcpImage = null;
		
		readFile();
		
		imageName = getImageFromRecipe(recipeObject);
	}
	
	/**
	 * Method - resetFile
	 * Description - Resets the file by deleting the old one and
	 * creating a new one
	 * @param fn
	 * @return True - Returns if the file reset is successful
	 * @return False - Returns if the file reset fails
	 */
	public boolean resetFile(String fn){
		boolean isFileChanged = false;
		isFileChanged = recipeFile.delete();
		
		fileName = RECIPE_PATH + fn;
		isFileChanged = openFile();
		
		return isFileChanged;
	}
	
	/**
	 * Method - getImageFromRecipe
	 * Description - Sets the path for the image using the
	 *               image file name from the recipe object
	 * @param rcp
	 * @return String
	 */
	private String getImageFromRecipe(RecipeData rcp){
		String image = rcp.getImageFileName();
		String imagePath = "";
		
		if(!image.isEmpty()){
			imagePath = IMAGE_PATH + image;
		}
		
		return imagePath;
	}
	
	/**
	 * Method - openImage
	 * Description - Checks for the existence of the image file and reads it into a BufferedImage
	 * @return BufferedImage
	 */
	public BufferedImage openImage(){
		
		if((imageName!= null && !imageName.isEmpty()) && 
			Files.exists(Paths.get(imageName))){
			try{
				rcpImage = ImageIO.read(new File(imageName));
			}
			catch(IOException io){
				io.getMessage();
				io.printStackTrace();
			}
		}
		
		return rcpImage;
	}
	
	/**
	 * Method - openFile
	 * Description - Method to set the File pointer with the given filename 
	 * @return boolean
	 * @throws IOException
	 */
	public boolean openFile(){
		boolean readIsSuccessful = false;
		try
		{
			// Try to open file
			this.recipeFile = new File(fileName);
			this.rcpReader = new BufferedReader(new FileReader(recipeFile));
			readIsSuccessful = true;
		}
		catch(FileNotFoundException fnf)
		{
			// If file does not exist, then create it
			try{
				BufferedWriter createFile = new BufferedWriter(new FileWriter(new File(fileName)));
				createFile.write("");
				createFile.close();
				
				this.rcpReader = new BufferedReader(new FileReader(recipeFile));
			}
			catch(IOException io){
				io.getMessage();
				io.printStackTrace();
				System.out.println("Error: Unable to create new recipe file.");
			}
		}
		
		return readIsSuccessful;
	} // openFile
	
	/**
	 * Method - readFile
	 * Description - Method to read data from file and store into a RecipeData object
	 * @return boolean
	 */
	public boolean readFile(){
		boolean readOK = false;
		
		String lineOfFile = null;
		
		try{
			
			if(rcpReader == null){
				rcpReader = new BufferedReader(new FileReader(recipeFile));
			}
			
			lineOfFile = rcpReader.readLine();
			
			while(lineOfFile != null && !lineOfFile.startsWith(END + RECIPE)){
				// Read each line of the .rcp file until there are no more to read
				// or the end recipe tag is found
				
				// Check the line tag
				if(lineOfFile.startsWith(BEGIN + RECIPE)){
					lineOfFile = rcpReader.readLine();
				}
				
				if(lineOfFile.startsWith(NAME)){
					recipeObject.setRecipeName(lineOfFile.substring(5));
				}
				else if(lineOfFile.startsWith(CATEGORY)){
					recipeObject.setCategory(lineOfFile.substring(5));
				}
				else if(lineOfFile.startsWith(IMAGE)){
					recipeObject.setImageFileName((lineOfFile.substring(5)));
				}
				else if(lineOfFile.startsWith(TAG)){
					recipeObject.setTags(lineOfFile.substring(5));
				}
				else if(lineOfFile.startsWith(BEGIN + INGREDIENTS)){
					recipeObject.setListOfIngredients(readIngredients(lineOfFile, rcpReader));
				}
				else if(lineOfFile.startsWith(BEGIN + DIRECTIONS)){
					recipeObject.setInstructions(readDirections(lineOfFile, rcpReader));
				}
				
				lineOfFile = rcpReader.readLine();
				
			} // while
			
			//rcpReader.close();
			
			readOK = true;
		} // try
		catch(FileNotFoundException fnf){
			fnf.printStackTrace();
			System.out.println(fnf.getMessage());
		}
		catch(IOException io){
			io.printStackTrace();
			System.out.println(io.getMessage());
		}
		
		return readOK;
	} // readFile
	
	/**
	 * Method - readIngredients
	 * 
	 * Description - Method to read all the ingredients from a file into a string
	 * @param line
	 * @param reader
	 * @return String
	 */
	private String readIngredients(String line, BufferedReader reader){
		StringBuilder ingredientsList = new StringBuilder();
		
		try{
			// Read the line after the begin ingredients tag
			line = reader.readLine();
			
			while(!line.startsWith(END + INGREDIENTS)){
				ingredientsList.append(line + "\n");
				
				line = reader.readLine();
			} // while
			
		}
		catch(IOException io){
			io.getMessage();
			io.printStackTrace();
		}
		
		return ingredientsList.toString();
	} // readIngredients
	
	/**
	 * Method - readDirections
	 * Description - Reads the directions from the .rcp file
	 * @param line
	 * @param reader
	 * @return String
	 */
	private String readDirections(String line, BufferedReader reader){
		StringBuilder directions = new StringBuilder();
		
		try{
			// Read the line after the begin directions tag
			line = reader.readLine();
			
			while(!line.startsWith(END + DIRECTIONS)){
				directions.append(line + "\n");
				
				line = reader.readLine();
			} // while
		}
		catch(IOException io){
			io.getMessage();
			io.printStackTrace();
		}
		
		return directions.toString();
		
	} // readDirections
	
	/**
	 * getData
	 * Description: Returns the RecipeData object from the file
	 * @return RecipeData
	 */
	public RecipeData getData()
	{
		return recipeObject;
	}
	
	/**
	 * Method - createRecipe
	 * Description - Assumes that the FileManager contains a instantiated
	 * RecipeData object. Initializes values for the recipe and
	 * saves the file to disk.
	 * @param recipeName
	 * @param category
	 * @param ingredients
	 * @param instructions
	 * @return boolean
	 */
	public boolean createRecipe
		(String recipeName, String category, String ingredients, 
		String instructions, String image, String tags){
		
		// Recipe object is instantiated
		// Simply set data fields
		recipeObject.setRecipeName(recipeName);
		recipeObject.setCategory(category);
		recipeObject.setListOfIngredients(ingredients);
		recipeObject.setInstructions(instructions);
		recipeObject.setImageFileName(image);
		recipeObject.setTags(tags);
		
		return closeFile();
	}
	
	/**
	 * Method - closeFile
	 * Description - Saves the recipe data to the File pointer and closes the file.
	 * @return boolean
	 */
	public boolean closeFile(){
		boolean closeIsSuccessful = false;
		
		try
		{
			writeData();
			closeIsSuccessful = true;
		}
		catch(IOException io)
		{
			io.getMessage();
			io.printStackTrace();
		}
		
		return closeIsSuccessful;
	}
	
	/**
	 * Method - writeData
	 * Description - Method to generate a String to write to file
	 * @return void
	 */
	private void writeData() throws IOException {
		rcpWriter = new BufferedWriter(new FileWriter(recipeFile));
		
		rcpWriter.write(generateTextData());
		
		// Close reader and writer
		rcpWriter.close();
		rcpReader.close();
	}
	
	/**
	 * Method - generateTextData
	 * Description - Method to convert the RecipeData object into a String
	 *               that can be written to a file
	 * @return String
	 */
	private String generateTextData(){
		StringBuilder textData = new StringBuilder();
		
		// Begin recipe tag
		textData.append(generateRecipeTag(true));
		
		// Name tag
		textData.append(generateName());
		
		// Category tag
		textData.append(generateCategory());
		
		// Image tag
		textData.append(generateImage());
		
		// Tags tag
		textData.append(generateTags());
		
		// Begin ingredients tag
		textData.append(generateIngredientsTag(true));
		// Add ingredients
		textData.append(generateIngredients());
		// End ingredients tag
		textData.append(generateIngredientsTag(false));
		
		// Begin directions tag
		textData.append(generateDirectionsTag(true));
		textData.append(generateDirections());
		textData.append(generateDirectionsTag(false));
		
		// End recipe tag
		textData.append(generateRecipeTag(false));
		
		return textData.toString();
	}
	
	/**
	 * Method - generateRecipeTag
	 * Description - Returns the begin recipe tag
	 * @param isBegin
	 * @return String
	 */
	private String generateRecipeTag(boolean isBegin){
		if(isBegin){
			return BEGIN + RECIPE + "\n";
		}
		else{
			return END + RECIPE + "\n";
		}
	}
	
	/**
	 * Method - generateName
	 * Description - Returns the name tag and recipe name
	 * @return String
	 */
	private String generateName(){
		return NAME + recipeObject.getRecipeName() + "\n";
	}
	
	/**
	 * Method - generateCategory
	 * Description - Returns the category tag and category name
	 * @return String
	 */
	private String generateCategory(){
		return CATEGORY + recipeObject.getCategory() + "\n";
	}
	
	/**
	 * Method - generateImage
	 * Description - Returns the image tag and image filename
	 * @return String
	 */
	private String generateImage(){
		return IMAGE + recipeObject.getImageFileName() + "\n";
	}
	
	/**
	 * Method - generateTags
	 * Description - Returns the tags tag and list of tags
	 * @return String
	 */
	private String generateTags(){
		return TAG + recipeObject.getTags() + "\n";
	}
	
	/**
	 * Method - generateIngredientsTag
	 * Description - Returns the begin/end tag and ingredients tag
	 * @return String
	 */
	private String generateIngredientsTag(boolean isBegin){
		if(isBegin){
			return BEGIN + INGREDIENTS + "\n";
		}
		else{
			return END + INGREDIENTS + "\n";
		}
	}
	
	/**
	 * Method - generateIngredients
	 * Description - Returns the ingredients
	 * @return String
	 */
	private String generateIngredients(){
		return recipeObject.getListOfIngredientsAsString() + "\n";
	}
	
	/**
	 * Method - generateDirectionsTag
	 * Description - Returns the begin tag and directions tag
	 * @param isBegin
	 * @return String
	 */
	private String generateDirectionsTag(boolean isBegin){
		if(isBegin){
			return BEGIN + DIRECTIONS + "\n";
		}
		else{
			return END + DIRECTIONS + "\n";
		}
	}
	
	/**
	 * Method - printData
	 * Description - Prints the RecipeData object data to the console
	 * @return void
	 */
	private void printData(){
		System.out.print(
			"Recipe name: " + recipeObject.getRecipeName() + "\n" +
			"Category: " + recipeObject.getCategory() + "\n" +
			"Image: " + recipeObject.getImageFileName() + "\n" +
			"Tags: " + recipeObject.getTags() + "\n" +
			"Ingredients: " + recipeObject.getListOfIngredients() + "\n" +
			"Directions: " + recipeObject.getInstructions() + "\n\n"
		);
	}
	
	/**
	 * Method - createString
	 * Description - Creates a string containing the recipe
	 * @return String
	 */
	public String createString(){
		StringBuilder dataAsString = new StringBuilder();
		dataAsString.append(
			"Recipe name: " + recipeObject.getRecipeName() + "\n" +
			"Category: " + recipeObject.getCategory() + "\n" +
			"Image: " + recipeObject.getImageFileName() + "\n" +
			"Tags: " + recipeObject.getTags() + "\n" +
			"Ingredients: \n" + recipeObject.getListOfIngredientsAsString() + "\n" +
			"Directions: \n" + recipeObject.getInstructions()
			);
		
		return dataAsString.toString();
	}
	
	/**
	 * Method - generateDirections
	 * Description - Returns the directions
	 * @return String
	 */
	private String generateDirections(){
		return recipeObject.getInstructions() + "\n";
	}
	
	/**
	 * Unit tester
	 * @param args
	 */
	public static void main(String[] args){
		
		char testToRun = 2;
		String testFileName = "testRead.rcp";
		
		// Create RecipeData object
		System.out.println("Creating RecipeData object...");
		RecipeData testRecipe = new RecipeData();
		
		// Create FileManager
		System.out.println("Creating FileManager object...");
		FileManager fm = new FileManager();
		
		
		switch(testToRun){
		case 1:
			// Create file and write
			
			// Set fields for RecipeData object
			testRecipe.setCategory("<Category here>");
			testRecipe.setImageFileName("<image filename here>");
			testRecipe.setIngredients("<ingredients list here>");
			testRecipe.setInstructions("<instructions list here>");
			testRecipe.setRecipeName("Test recipe");
			testRecipe.setTags("<tags list here>");
			
			// Set object to manager
			fm.recipeObject = testRecipe;
			// Write data to file
			System.out.println("Writing RecipeData object to file...");
			fm.closeFile();
			
			// Delete file
			// fm.recipeFile.delete();
			break;
			
		case 2:
			// Read from file and print data
			fm = new FileManager(testFileName);
			
			if(fm.readFile()){
				fm.printData();
			}
			else{
				System.out.println("Error: File not read successfully. Unable to print data.");
			}
			break;
			
		default:
			System.out.println("Error: Select a test.");
			break;
		} // switch
		

		
		System.out.println("\nEnd of unit test\n");
		
	}
} // class
